package SSF.Net;

import SSF.Net.Util.*;
import com.renesys.raceway.OutOfCore.*;
import java.io.*;

/** Forwarding data stored in a node in a RoutingTable (out of core). */
public class RoutingInfoOOC extends RoutingInfo {

  private static final int NEXTHOP = 0; // Administrative Distance index
  private static final int COST    = 1; // cost index
  private static final int ADIST   = 2; // next hop index

  /** Reference to the network interface that gets us to the next hop */
  protected NIC INTERFACE;
  
  /** Linked list of routes to the same destination IP address */
  protected RoutingInfoOOC nextRoute;

  private Segment seg;

  public RoutingInfoOOC(String dest_ip, int next_hop, NIC iface, int cost,
                        int adist, String src) {
    INTERFACE = iface;
    nextRoute = null;

    synchronized (SegmentFactory.lock) {
      try {
        seg = Net.heap.malloc(1000);
        DataOutput out = seg.getOutput();
        seg.seek();
        //ByteArrayOutputStream bs = new ByteArrayOutputStream();
        //DataOutputStream bout = new DataOutputStream(bs);
        out.writeInt(next_hop);
        out.writeInt(cost);
        out.writeInt(adist);
        out.writeUTF(dest_ip);
        out.writeUTF(src); // the originating protocol
        //bout.writeInt(next_hop);
        //bout.writeInt(cost);
        //bout.writeInt(adist);
        //bout.writeUTF(dest_ip);
        //bout.writeUTF(src); // the originating protocol
        //System.out.println("routinginfo size: "+bs.size());
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  public void finalize() throws java.lang.Throwable {
    if (seg != null) {
      try {
        seg.free();
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
    super.finalize();
  }

  public final NIC next_hop_interface() {
    return INTERFACE;
  }

  public final String dest_ip() {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      try {
        seg.seek();
        in.readInt(); // skip next_hop
        in.readInt(); // skip cost
        in.readInt(); // skip adist
        return in.readUTF(); // get dest_ip
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return null;
    }
  }

  public final int next_hop_ip() {
    synchronized (SegmentFactory.lock) {
      try {
        seg.seek();
        return (seg.getInput()).readInt();
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return -1;
    }
  }

  /** Returns the cost. */
  public int cost() {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      try {
        seg.seek();
        in.readInt(); // skip next_hop
        return in.readInt(); // get cost
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return -1;
    }
  }

  /** Returns the administrative distance. */
  public int adist() {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      try {
        seg.seek();
        in.readInt(); // skip next_hop
        in.readInt(); // skip cost
        return in.readInt(); // get adist
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return -1;
    }
  }

  public RoutingInfo nextRoute() {
    return nextRoute;
  }

  public final String getProtocol() {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      try {
        seg.seek();
        in.readInt(); // skip next_hop
        in.readInt(); // skip cost
        in.readInt(); // skip adist
        in.readUTF(); // skip dest_ip
        return in.readUTF(); // get protocol
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return null;
    }
  }

  private int[] getInts() {
    synchronized (SegmentFactory.lock) {
      int[] ret = new int[3];
      DataInput in = seg.getInput();
      try {
        seg.seek();
        ret[0] = in.readInt(); // next_hop
        ret[1] = in.readInt(); // cost
        ret[2] = in.readInt(); // adist
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return ret;
    }
  }

  private Object[] getAll() {
    synchronized (SegmentFactory.lock) {
      Object[] ret = new Object[3];
      int[] ints = new int[3];
      DataInput in = seg.getInput();
      try {
        seg.seek();
        ints[0] = in.readInt(); // next_hop
        ints[1] = in.readInt(); // cost
        ints[2] = in.readInt(); // adist
        ret[0] = ints;
        ret[1] = in.readUTF(); // dest_ip
        ret[2] = in.readUTF(); // protocol
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return ret;
    }
  }

  /** Insert one or more new routes into the linked list, sorted primarily by
   *  administrative distance and secondarily by cost.  Return the new head
   *  route (either the new route, or this route, whichever is lower-cost).
   */
  public RoutingInfo addRoute(RoutingInfo newRte) {
    RoutingInfoOOC newRoute = (RoutingInfoOOC)newRte;
    RoutingInfo ret = null;
    RoutingInfoOOC moreRoutes = (RoutingInfoOOC)newRoute.nextRoute;
    newRoute.nextRoute = null;
    int[] newInts = newRoute.getInts();
    int[] curInts = getInts();
    if (newInts[ADIST]<curInts[ADIST] ||
        (newInts[ADIST]==curInts[ADIST] && newInts[COST]<=curInts[COST])) {
      newRoute.nextRoute = this;
      ret = newRoute;
    } else {
      nextRoute = (RoutingInfoOOC)(nextRoute==null?newRoute:
                                   nextRoute.addRoute(newRoute));
      ret = this;
    }
    return (moreRoutes==null?ret:ret.addRoute(moreRoutes));
  }

  /** Remove the given route, and return the new head route (either this
   *  route, or if this route was removed, the next route). 
   */
  public RoutingInfo removeRoute(RoutingInfo oldRoute) {
    if (oldRoute.equals(this)) {
      RoutingInfo ret = nextRoute;
      nextRoute = null;
      return ret;
    }
    else {
      nextRoute = (RoutingInfoOOC)nextRoute.removeRoute(oldRoute);
      return this;
    }
  }

  /** Remove routes from the given protocol, and return the new head route 
   * (either this route, or if this route was removed, the next route). 
   *  The special protocol name "*" matches all protocols.  If the list
   *  argument is non-null, insert deleted routes into the list. 
   */
  public RoutingInfo removeRoutesFrom(String protocol) {
    String myProtocol = getProtocol();
    if ("*".equals(protocol) ||
        (null!=myProtocol && myProtocol.equals(protocol))) 
      return (null==nextRoute?null:nextRoute.removeRoutesFrom(protocol));
    else {
      if (null!=nextRoute) 
        nextRoute = (RoutingInfoOOC)nextRoute.removeRoutesFrom(protocol);
      return this;
    }
  }

  /** Return the list of routes inserted by the given protocol.  
   *  The special protocol name "*" matches all protocols. 
   */
  public RoutingInfo[] findRoutesFrom(String protocol) {
    int len =0; 
    for (RoutingInfoOOC rif = this; null!=rif; rif=rif.nextRoute) {
      String proto = rif.getProtocol();
      if ("*".equals(protocol) || protocol.equals(proto)) 
        len++;
    }
    RoutingInfo[] ret = new RoutingInfo[len];
    len=0;
    for (RoutingInfoOOC rif = this; null!=rif; rif=rif.nextRoute) {
      String proto = rif.getProtocol();
      if ("*".equals(protocol) || protocol.equals(proto)) 
        ret[len++]=rif;
    }
    return ret;
  }
 
  /** Find the first (best) route inserted by the named protocol. */
  public RoutingInfo findRouteFrom(String protocol) {
    if ("*".equals(protocol) || protocol.equals(getProtocol())) 
      return this;
    else 
      return (null==nextRoute?null:nextRoute.findRouteFrom(protocol));
  }

  /**
   * Returns the routing information as a string.
   *
   * @return the routing information as a string
   */
   public String toString() {
     return toString(false, null);
   }

  /**
   * Returns the routing information as a string.
   *
   * @param usenhi    Whether to use the NHI or IP prefix address format.
   * @param topnet    The top-level Net in the simulation.
   * @return the routing information as a string
   */
  public String toString(boolean usenhi, Net topnet) {
    Object[] objs = getAll();
    int[] ints = (int[])objs[0];
    String dest_ip = (String)objs[1];
    String protocol = (String)objs[2];

    if (usenhi) {
      String nexthop = null;
      nexthop = topnet.ip_to_nhi(IP_s.IPtoString(ints[NEXTHOP]));
      if (nexthop == null) {
        nexthop = "none";
      }
      String dest = topnet.ip_to_nhi(dest_ip);
      if (dest == null || dest.indexOf("-") >= 0) {
        // there was no NHI equivalent for the IP prefix
        dest = dest_ip;
      }
      String ws = "                 "; // 17 spaces
      return (dest + ws.substring(0,Math.max(17-dest.length(),1)) +
              " nxt=" + nexthop + " cost=" + ints[COST] + " adist="+
              ints[ADIST] + " if=" +
              topnet.ip_to_nhi(IP_s.IPtoString(next_hop_interface().ipAddr)) +
              " src=" + protocol);
    } else {
      return ("{dst=" + dest_ip + " :nxt=" + IP_s.IPtoString(ints[NEXTHOP]) +
              " :cost=" + ints[COST] + " :adist=" + ints[ADIST] +
              " :if=" + next_hop_interface() +" :src=" + protocol + "}");
    }
  }

  /**
   * Converts this routing info into a series of bytes and inserts them into a
   * given byte array.
   *
   * @param bytes   A byte array in which to place the results.
   * @param bindex  The index into the given byte array at which to begin
   *                placing the results.
   * @param usenhi  Whether or not to use NHI addressing.
   * @param topnet  The top-level Net in the simulation.
   * @return the total number of bytes produced by the conversion
   */
  public int toBytes(byte[] bytes, int bindex, boolean usenhi, Net topnet) {
    Object[] objs = getAll();
    int[] ints = (int[])objs[0];
    String dest_ip = (String)objs[1];
    String protocol = (String)objs[2];

    int startindex = bindex;

    if (ints[NEXTHOP] == 0) {
      bytes[bindex++] = 0; // indicate that next hop is unknown
    } else {
      bytes[bindex++] = 1; // indicate that next hop is known
    }

    if (usenhi) {
      if (ints[NEXTHOP] != 0) {
        String nexthop = topnet.ip_to_nhi(IP_s.IPtoString(ints[NEXTHOP]));
        bindex += RadixTreeRoutingTable.nhi2bytes(nexthop,bytes,bindex);
      }
      bytes[bindex++] = (byte)ints[COST];
      bytes[bindex++] = (byte)ints[ADIST];
      bytes[bindex++] = RoutingInfo.encodeSource(protocol);
      String iface = topnet.ip_to_nhi(IP_s.IPtoString(next_hop_interface().
                                                                      ipAddr));
      bindex += RadixTreeRoutingTable.nhi2bytes(iface,bytes,bindex);
    } else {
      if (ints[NEXTHOP] != 0) {
        bindex += RadixTreeRoutingTable.ipprefix2bytes(ints[NEXTHOP],32,
                                                       bytes,bindex);
      }
      bytes[bindex++] = (byte)ints[COST];
      bytes[bindex++] = (byte)ints[ADIST];
      bytes[bindex++] = RoutingInfo.encodeSource(protocol);
      bindex += RadixTreeRoutingTable.ipprefix2bytes(next_hop_interface().
                                                     ipAddr,32,bytes,bindex);
    }
    return bindex - startindex;
  }


} // end class RoutingInfoOOC



/*=                                                                      =*/
/*=  Copyright (c) 1997--2000  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
